#include "log.hpp"
#include <iostream>
#include <cstdio>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <queue>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <wait.h>

#define SIZE 1024
#define BACKLOG 5


//介绍：多进程版，是调用了fork函数产生子进程，让子进程来服务客户端。

//父进程需要等待子进程时：在父进程需要等待回收子进程的情况下，父进程是无法继续appcet获取下一个客户端的连接的，这就导致了，服务端一次只能服务一个客户端，
//必须等待该客户端服务完毕之后才能继续获取下一个客户端的连接，对其进行服务。
//(假若是轮询式等待的话，按原理上来说是可以实现多进程，父进程可以边去获取新的连接，边询问子进程，对需要回收的子进程进行的回收，但这种轮询式的等待每次都要去询问，
//是非常消耗资源的，所以我们也不推荐用这种做法)

//父进程不需要等待子进程时：在父进程不用等待回收子进程的情况下，父进程就可以直接获取下一个客户端的连接，然后再交给子进程去提供服务。以此类推，父进程可以不断获取连接，
//然后派子进程去服务该连接对应的客户端。这就形成了多进程的版本

//实现多进程的版本有以下三种方法：
///////////////////////////////////////////////////////////////////////////////////////////////////////
//1.让孙子进程提供服务
//
//  父进程fork产生子进程，子进程再fork产生孙子进程。然后让子进程退出即可。此时父进程可以直接回收子进程，不需要再等待。
//  此时孙子进程就变为了孤儿进程，在完成任务后孙子进程退出，此时他不会变成僵尸进程，而是变成孤儿进程，由bash1号进程接纳为子进程，并对其进行回收。
//         
/////////////////////////////////////////////////////////////////////////////////////////////////////////







class TcpServer
{
public:
    TcpServer(uint16_t port, std::string ip = "")
        : _port(port),
          _ip(ip),
          _listen_sock(-1)
    {
        ;
    }

    void initServer()
    {
        // 1.创建套接字
        _listen_sock = socket(AF_INET, SOCK_STREAM, 0); // 协议家族，服务类型(tcp是字节流，udp是数据报头)，协议类别(指明是tcp or udp，指明会自己默认自动识别)
        if (_listen_sock < 0)
        {
            logMessage(FATAL, "%d,%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "socket success ... sockfd:%d ,strerror:%s", _listen_sock, strerror(errno));

        struct sockaddr_in local;     // 写入服务端信息
        bzero(&local, sizeof(local)); // 将local全部字节清0

        ////struct sockaddr_in  本质
        //{
        // unsigned short int sin_family;
        // unsigned short int sin_port;
        // unsigned int sin_ip;
        //}
        ////

        local.sin_family = AF_INET;
        // host to net by short.由 主机序列(unsigned short int) 转为 网络字节序 (unsigned short int)
        local.sin_port = htons(_port);
        //_ip若为空，则指定从任意ip上接收数据。若_ip不为空，则将ip由 主机序列(点分十进制的字符串(c语言风格))类型 转为 网络序列(unsigned int)
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());

        // 2.绑定
        if (bind(_listen_sock, (struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, " %d %s ", errno, strerror(errno)); // 表示绑定失败，说明指定的ip和port 与 指定的套接字 绑定失败了
            exit(3);
        }
        logMessage(NORMAL, "bind success ... %s", strerror(errno));

        // 3.设置监听状态并设置等待队列长度，为accpet获取链接做准备
        if (listen(_listen_sock, BACKLOG) < 0)
        {
            logMessage(ERROR, "listen error ... %s", strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "listen set success ... %s", strerror(errno));

        logMessage(NORMAL, "init tcp server done ... %s", strerror(errno));
        // done
        return;
    }

    void Service(int sock, std::string client_ip, int client_port)
    {
        char buffer[1024];
        while (true)
        {
            ssize_t size = read(sock, buffer, sizeof(buffer) - 1);
            if (size > 0)
            {
                // 读取成功
                buffer[size] = '\0';
                std::cout << "get a new link-> " << sock << " [" << client_ip << "] " << client_port <<"# "<<buffer<<std::endl;

                write(sock, buffer, size);
            }
            else if (size == 0)
            {
                // 对端连接关闭
                std::cout << client_ip << ":" << client_ip << " close!" << std::endl;
                break;
            }
            else
            {
                // 读取失败
                std::cerr << sock << " read error!" << std::endl;
                break;
            }
        }
        close(sock); // 归还文件描述符
        std::cout << client_ip << ":" << client_port << " server done!" << std::endl;
    }
    void Start()
    {
        char buffer[SIZE]; // 用于接收客户端发来的数据
        while (true)       // 永远不会退出，除非服务端挂了
        {
            // 4.accpet获取连接

            struct sockaddr_in peer; // 用于存放获取到的连接中的对端的网络相关的属性信息
            bzero(&peer, sizeof(peer));
            socklen_t len = sizeof(peer);

            int _server_sock = accept(_listen_sock, (struct sockaddr *)&peer, &len); // 输入型参数-监听套接字，输出型参数-对端网络信息，输入输出型参数-peer的大小

            if (_server_sock < 0)
            {
                logMessage(ERROR,"accpet error,continue next");
                continue;
            }

            std::string client_ip = inet_ntoa(peer.sin_addr); // 结构体封装的unsigned int 转 字符串char*。（字符串char*会隐式类型转化为string)
            uint16_t client_port = ntohs(peer.sin_port);      // unsigned short int 转 unsigned short int
            std::cout << "get a new link-> " << _server_sock << " [" << client_ip << "] " << client_port << std::endl;
            pid_t id1=fork();
            if(id1==0)
            {
                //child
                pid_t id2=fork();
                if(id2>0)
                {
                    //child
                    close(_server_sock);//因为fork会复制进程的文件描述符表，fork之后父子进程的文件描述符表是一样的，而child进行不需要做什么事情，所以直接关掉child进行即可
                    exit(0);
                }
                else if(id2==0)
                {
                    //grandson
                    Service(_server_sock, client_ip, client_port);
                    exit(0);//处理完该任务之后，子进程直接退出即可，稍后会由内核回收了，因为调用了signal对sigchld信号进行了捕捉。
                }
            }
            //father
            close(_server_sock);//同child进程关闭_server_sock
            // waitpid(id1,nullptr,0);//父进程等待child进行的退出（直接等待成功，不需要再继续等待）
            waitpid(id1,NULL,0);//父进程等待child进行的退出（直接等待成功，不需要再继续等待）

        }
    }
    ~TcpServer()
    {
        if (_listen_sock >= 0)
            close(_listen_sock);
    }

private:
    uint16_t _port;   // 端口号 unsigned short int
    std::string _ip;  // ip地址
    int _listen_sock; // 套接字
};
